package personnal.blogprinter.htmlparser;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Shape;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xhtmlrenderer.css.style.CalculatedStyle;
import org.xhtmlrenderer.extend.NamespaceHandler;
import org.xhtmlrenderer.extend.UserInterface;
import org.xhtmlrenderer.layout.BoxBuilder;
import org.xhtmlrenderer.layout.Layer;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.pdf.ITextFontContext;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextOutputDevice;
import org.xhtmlrenderer.pdf.ITextReplacedElementFactory;
import org.xhtmlrenderer.pdf.ITextTextRenderer;
import org.xhtmlrenderer.pdf.ITextUserAgent;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.render.Box;
import org.xhtmlrenderer.render.PageBox;
import org.xhtmlrenderer.render.RenderingContext;
import org.xhtmlrenderer.render.ViewportBox;
import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler;
import org.xhtmlrenderer.util.Configuration;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfWriter;

public class MyITextRenderer {

    // These two defaults combine to produce an effective resolution of 96 px to the inch
    private static final float DEFAULT_DOTS_PER_POINT = 20f * 4f / 3f;
    private static final int DEFAULT_DOTS_PER_PIXEL = 20;

    private SharedContext sharedContext;
    private ITextOutputDevice outputDevice;

    private Document doc;
    private Box root;

    private float _dotsPerPoint;

    public MyITextRenderer() {
        this(DEFAULT_DOTS_PER_POINT, DEFAULT_DOTS_PER_PIXEL);
    }

    public MyITextRenderer(float dotsPerPoint, int dotsPerPixel) {
        this._dotsPerPoint = dotsPerPoint;

        this.outputDevice = new ITextOutputDevice(this._dotsPerPoint);

        ITextUserAgent userAgent = new ITextUserAgent(this.outputDevice);
        this.sharedContext = new SharedContext(userAgent);
        userAgent.setSharedContext(this.sharedContext);
        this.outputDevice.setSharedContext(this.sharedContext);

        ITextFontResolver fontResolver = new ITextFontResolver(this.sharedContext);
        this.sharedContext.setFontResolver(fontResolver);

        ITextReplacedElementFactory replacedElementFactory = new ITextReplacedElementFactory(this.outputDevice);
        this.sharedContext.setReplacedElementFactory(replacedElementFactory);

        this.sharedContext.setTextRenderer(new ITextTextRenderer());
        this.sharedContext.setDPI(72 * this._dotsPerPoint);
        this.sharedContext.setDotsPerPixel(dotsPerPixel);
        this.sharedContext.setPrint(true);
        this.sharedContext.setInteractive(false);
    }

    public ITextFontResolver getFontResolver() {
        return (ITextFontResolver) this.sharedContext.getFontResolver();
    }

    public void setDocumentFromString(String content) throws ParserConfigurationException, SAXException, IOException {
        DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        InputSource is = new InputSource(new BufferedReader(new StringReader(content)));
        documentBuilder.setEntityResolver(new HtmlEntityResolver());
        Document dom = documentBuilder.parse(is);
        setDocument(dom, null, new XhtmlNamespaceHandler());
    }

    public void setDocument(Document doc, String url, NamespaceHandler nsh) {
        this.doc = doc;

        getFontResolver().flushFontFaceFonts();
        this.sharedContext.reset();
        if (Configuration.isTrue("xr.cache.stylesheets", true)) {
            this.sharedContext.getCss().flushStyleSheets();
        } else {
            this.sharedContext.getCss().flushAllStyleSheets();
        }
        this.sharedContext.setBaseURL(url);
        this.sharedContext.setNamespaceHandler(nsh);
        this.sharedContext.getCss().setDocumentContext(this.sharedContext, this.sharedContext.getNamespaceHandler(),
                doc, new NullUserInterface());
        getFontResolver().importFontFaces(this.sharedContext.getCss().getFontFaceRules());
    }

    public void layout() {
        LayoutContext c = newLayoutContext();
        BlockBox root = BoxBuilder.createRootBox(c, this.doc);
        root.setContainingBlock(new ViewportBox(getInitialExtents(c)));
        root.layout(c);
        Dimension dim = root.getLayer().getPaintingDimension(c);
        root.getLayer().trimEmptyPages(c, dim.height);
        root.getLayer().layoutPages(c);
        this.root = root;
    }

    private Rectangle getInitialExtents(LayoutContext c) {
        PageBox first = Layer.createPageBox(c, "first");

        return new Rectangle(0, 0, first.getContentWidth(c), first.getContentHeight(c));
    }

    private RenderingContext newRenderingContext() {
        RenderingContext result = this.sharedContext.newRenderingContextInstance();
        result.setFontContext(new ITextFontContext());

        result.setOutputDevice(this.outputDevice);

        this.sharedContext.getTextRenderer().setup(result.getFontContext());

        result.setRootLayer(this.root.getLayer());

        return result;
    }

    private LayoutContext newLayoutContext() {
        LayoutContext result = this.sharedContext.newLayoutContextInstance();
        result.setFontContext(new ITextFontContext());

        this.sharedContext.getTextRenderer().setup(result.getFontContext());

        return result;
    }

    private void writePDF(List<PageBox> pages, RenderingContext c, com.lowagie.text.Rectangle firstPageSize,
            com.lowagie.text.Document document, PdfWriter writer) throws DocumentException {
        this.outputDevice.setRoot(this.root);

        this.outputDevice.start(this.doc);
        this.outputDevice.setWriter(writer);
        this.outputDevice.initializePage(writer.getDirectContent(), firstPageSize.getHeight());

        this.root.getLayer().assignPagePaintingPositions(c, Layer.PAGED_MODE_PRINT);

        int pageCount = this.root.getLayer().getPages().size();
        c.setPageCount(pageCount);
        for (int i = 0; i < pageCount; i++) {
            PageBox currentPage = pages.get(i);
            c.setPage(i, currentPage);
            paintPage(c, currentPage);
            this.outputDevice.finishPage();
            if (i != pageCount - 1) {
                PageBox nextPage = pages.get(i + 1);
                com.lowagie.text.Rectangle nextPageSize = new com.lowagie.text.Rectangle(0, 0, nextPage.getWidth(c)
                        / this._dotsPerPoint, nextPage.getHeight(c) / this._dotsPerPoint);
                document.setPageSize(nextPageSize);
                document.newPage();
                this.outputDevice.initializePage(writer.getDirectContent(), nextPageSize.getHeight());
            }
        }

        this.outputDevice.finish(c, this.root);
    }

    private void paintPage(RenderingContext c, PageBox page) {
        page.paintBackground(c, 0, Layer.PAGED_MODE_PRINT);
        page.paintMarginAreas(c, 0, Layer.PAGED_MODE_PRINT);
        page.paintBorder(c, 0, Layer.PAGED_MODE_PRINT);

        Shape working = this.outputDevice.getClip();

        Rectangle content = page.getPrintClippingBounds(c);
        this.outputDevice.clip(content);

        int top = -page.getPaintingTop() + page.getMarginBorderPadding(c, CalculatedStyle.TOP);

        int left = page.getMarginBorderPadding(c, CalculatedStyle.LEFT);

        this.outputDevice.translate(left, top);
        this.root.getLayer().paint(c);
        this.outputDevice.translate(-left, -top);

        this.outputDevice.setClip(working);
    }

    /**
     * <B>NOTE:</B> Caller is responsible for cleaning up the OutputStream if something goes wrong.
     */
    public void createPDF(com.lowagie.text.Document document, PdfWriter pdfWriter) throws DocumentException {
        @SuppressWarnings("unchecked")
        List<PageBox> pages = this.root.getLayer().getPages();

        RenderingContext c = newRenderingContext();
        PageBox firstPage = pages.get(0);
        com.lowagie.text.Rectangle firstPageSize = new com.lowagie.text.Rectangle(0, 0, firstPage.getWidth(c)
                / this._dotsPerPoint, firstPage.getHeight(c) / this._dotsPerPoint);

        // _pdfDoc = document;
        // _writer = pdfWriter;

        writePDF(pages, c, firstPageSize, document, pdfWriter);
    }

    private static final class NullUserInterface implements UserInterface {
        public boolean isHover(Element e) {
            return false;
        }

        public boolean isActive(Element e) {
            return false;
        }

        public boolean isFocus(Element e) {
            return false;
        }
    }
}
